/**
 * 
 */
package Verifier.ArithmeticSystem;

import java.math.BigInteger;

/**
 * This class is used to implement most of the common functionality of the product of groups element.
 * It allows the inheritance of it and implementing only the way the components of the product are
 * being saved.
 * 
 * @author Moshe Baavur
 *
 */
public abstract class ProductElement extends GroupElement {
	// the group which the element belongs to
	protected ProductGroup group;
	
	/**
	 * Initializes a product element with the group sent. The group must be of type ProductGroup.
	 * 
	 * @param group the group which the element belongs to.
	 * @throws Exception if the group is not of type ProductGroup.
	 */
	public ProductElement(Group group) throws Exception {
		if (!(group instanceof ProductGroup)) {
			throw new Exception("The group for the product is not a product group");
		}
		this.group = (ProductGroup)group;
	}
	
	/**
	 * Adds a group element into the product at the last index.
	 * 
	 * @param element the group element to add - can be an element or another product.
	 * @throws Exception if adding the element exceeds the size of the product or if the element doesn't match the product's containing group.
	 */
	abstract public void addGroupElement(GroupElement element) throws Exception;
	
	/**
	 * Returns the element in the product element which has index which is equal to the passed parameter.
	 * 
	 * @param index the index of the object to return.
	 * @return the element at the specified index in the product element.
	 * @throws IndexOutOfBoundsException if index is out of the ranges of the product element - (index < 0 || index >= getNumberOfElements()).
	 */
	abstract public GroupElement getElement(int index) throws IndexOutOfBoundsException;
	
	/**
	 * Returns the number of elements in the product element.
	 * 
	 * @return the number of elements in the product element.
	 */
	abstract public int getNumberOfElements();

	abstract protected ProductElement createEmpty();
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#multiply(Verifier.ArithmeticSystem.GroupElement)
	 */
	@Override
	public GroupElement multiply(GroupElement other) throws Exception {
		ProductElement result = createEmpty();
		int length = getNumberOfElements();
		// now, divide this into two options
		if (other instanceof ProductElement) {
			// other is of type product element
			// check for similar sizes
			ProductElement otherProductElement = (ProductElement)other;
			if (otherProductElement.getNumberOfElements() != length) {
				throw new Exception("The product elements are of different sizes");
			}
			// now, run over the two product elements on the same time and multiply elements with the same index
			for (int i = 0; i < length; i++) {
				// multiply the object at index i by other - it might also be a product element
				GroupElement elem = getElement(i).multiply(otherProductElement.getElement(i));
				// add the result to the new product element
				result.addGroupElement(elem);
			}
		} else {
			// just need to multiply each element in the product element with this group element
			for (int i = 0; i < length; i++) {
				// multiply the object at index i by other - it might also be a product element
				GroupElement elem = getElement(i).multiply(other);
				// add the result to the new product element
				result.addGroupElement(elem);
			}
		}
		
		return (result);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#power(java.math.BigInteger)
	 */
	@Override
	public GroupElement power(BigInteger exponent) {		
		ProductElement result = createEmpty();
		int length = getNumberOfElements();
		
		// just need to multiply each element in the product element with this group element
		for (int i = 0; i < length; i++) {
			// multiply the object at index i by other - it might also be a product element
			GroupElement elem = getElement(i).power(exponent);
			// add the result to the new product element
			try {
				result.addGroupElement(elem);
			} catch (Exception e) {
				// no exception should be thrown to get us here cause we made the calculations
			}
		}
		
		return (result);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#divide(Verifier.ArithmeticSystem.GroupElement)
	 */
	@Override
	public GroupElement divide(GroupElement other) throws Exception {		
		ProductElement result = createEmpty();
		int length = getNumberOfElements();
		// now, divide this into two options
		if (other instanceof ProductElement) {
			// other is of type product group element
			// check for similar sizes
			ProductElement otherProductElement = (ProductElement)other;
			if (otherProductElement.getNumberOfElements() != length) {
				throw new Exception("The product elements are of different sizes");
			}
			// now, run over the two product elements on the same time and divide elements with the same index
			for (int i = 0; i < length; i++) {
				// divide the object at index i by other - it might also be a product element
				GroupElement elem = getElement(i).divide(otherProductElement.getElement(i));
				// add the result to the new product element
				result.addGroupElement(elem);
			}
		} else {
			// just need to divide each element in the product element with this group element
			for (int i = 0; i < length; i++) {
				// multiply the object at index i by other - it might also be a product element
				GroupElement elem = getElement(i).divide(other);
				// add the result to the new product element
				result.addGroupElement(elem);
			}
		}
		
		return (result);
	}
	
	/**
	 * Compare this to obj. They are equal only if obj is of type ProductElement and their group and sub-elements are equal.
	 * 
	 * @param obj the object to compare to this.
	 * @return true if and only if obj is of type ProductElement and the Group and the sub-elements are equal to this.
	 */
	@Override
	public boolean equals(Object obj) {
		// if the object is a ProductElement, then compare them using their elements and groups and not the address
		if (obj instanceof ProductElement) {
			ProductElement objElement = (ProductElement)obj;
			// check that the groups of the objects are the same
			if (!group.equals(objElement.group))
				return (false);
			
			int length = getNumberOfElements();
			if (objElement.getNumberOfElements() != length) {
				return (false);
			}
			
			for (int i = 0; i < length; i++) {
				// check if the product elements have the same elements inside them
				if (!objElement.getElement(i).equals(getElement(i))) {
					return (false);
				}
			}
			
			return (true);
		}
		// otherwise, they are not equal
		return (false);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.GroupElement#getGroup()
	 */
	@Override
	public Group getGroup() {
		// return the group of the product
		return (group);
	}
}
